#pragma once
#include <cstring>
#include <csv2/mio.hpp>
#include <istream>
#include <string>
#include <utility>

namespace csv {

	namespace trim_policy {
		struct no_trimming {
		public:
			template<typename T>
			static std::pair<size_t, size_t> trim(const T *buffer, size_t start, size_t end) {
				(void)(buffer); // to silence unused parameter warning
				return { start, end };
			}
		};

		template <typename T, T... character_list> struct trim_characters {
		private:
			constexpr static bool is_trim_char(T) { return false; }

			template <class... Tail> constexpr static bool is_trim_char(T c, T head, Tail... tail) {
				return c == head || is_trim_char(c, tail...);
			}

		public:
			static std::pair<size_t, size_t> trim(const T *buffer, size_t start, size_t end) {
				size_t new_start = start, new_end = end;
				while (new_start != new_end && is_trim_char(buffer[new_start], character_list...))
					++new_start;
				while (new_start != new_end && is_trim_char(buffer[new_end - 1], character_list...))
					--new_end;
				return { new_start, new_end };
			}
		};

		template <typename T>using trim_whitespace = trim_characters<T, ' ', '\t'>;
	} // namespace trim_policy

	inline const char* memchr(char* _Pv, int _C, size_t _N) {
		return static_cast<const char *>(::memchr(_Pv, _C, _N));
	}

	inline const wchar_t* memchr(wchar_t* _Pv, int _C, size_t _N) {
		return static_cast<const wchar_t *>(::wmemchr(_Pv, _C, _N));
	}

	template <typename T> struct CSVInfos
	{
		char delimiter = ',';
		char quote_character = '"';
		bool first_row_is_header = false;
		size_t current_row_end = 0;
		size_t next_row_start = 0;
		const T *buffer = nullptr;
		size_t buffer_size = 0;
	};

	template <typename T = char, class trim_policy = trim_policy::no_trimming>
	class Reader {
		mio::mmap_source mmap_;          // mmap source
		const T *buffer_{ nullptr };    // pointer to memory-mapped data
		size_t buffer_size_{ 0 };          // mapped length of buffer
		size_t header_start_{ 0 };         // start index of header (cache)
		size_t header_end_{ 0 };           // end index of header (cache)
		size_t bom_size_{ 0 };

	public:
		// Use this if you'd like to mmap the CSV file
		template <typename StringType> bool mmap(StringType &&filename) {
			mmap_ = mio::mmap_source(filename);
			if (!mmap_.is_open() || !mmap_.is_mapped())
				return false;
			buffer_ = reinterpret_cast<const T*>(mmap_.data());
			buffer_size_ = mmap_.mapped_length() / sizeof(T);
			infos.buffer = buffer_;
			infos.buffer_size = buffer_size_;
			return true;
		}

		// Use this if you have the CSV contents
		// in an std::string already
		template <typename StringType> bool parse(StringType &&contents) {
			buffer_ = std::forward<StringType>(contents).c_str();
			buffer_size_ = contents.size();
			infos.buffer = buffer_;
			infos.buffer_size = buffer_size_;
			return buffer_size_ > 0;
		}

		template <typename CharType> bool parse(const CharType *head, size_t bytes) {
			buffer_ = reinterpret_cast<const T*>(head);
			buffer_size_ = bytes / sizeof(T);
			infos.buffer = buffer_;
			infos.buffer_size = buffer_size_;
			return buffer_size_ > 0;
		}

		class RowIterator;
		class Row;
		class CellIterator;
		CSVInfos<T> infos;

		class Cell {
			const T *buffer_{ nullptr }; // Pointer to memory-mapped buffer
			size_t start_{ 0 };             // Start index of cell content
			size_t end_{ 0 };               // End index of cell content
			CSVInfos<T> *infos_{ nullptr };
			friend class Row;
			friend class CellIterator;

		public:
			// Returns the raw_value of the cell without handling escaped
			// content, e.g., cell containing """foo""" will be returned
			// as is
			template <typename Container> void read_raw_value(Container &result) const {
				if (start_ >= end_)
					return;
				result.reserve(end_ - start_);
				for (size_t i = start_; i < end_; ++i)
					result.push_back(buffer_[i]);
			}

			// If cell is escaped, convert and return correct cell contents,
			// e.g., """foo""" => ""foo""
			template <typename Container> void read_value(Container &result) const {
				if (start_ >= end_)
					return;
				result.reserve(end_ - start_);
				const auto new_start_end = trim_policy::trim(buffer_, start_, end_);
				for (size_t i = new_start_end.first; i < new_start_end.second; ++i)
					result.push_back(buffer_[i]);
				if (result.size() && result[0] == infos_->quote_character) {
					result.erase(0, 1);
					for (size_t i = 0; i < result.size(); ++i) {
						if (result[i] == infos_->quote_character) {
							result.erase(i, 1);
							if (result[i] != infos_->quote_character) { break; }
						}
					}
				}
			}

			inline const T * data() const {
				if (start_ >= end_) { return nullptr; }
				else { return &buffer_[start_]; }
			}
			inline const size_t size() const {
				return end_ - start_;
			}
		};

		class Row {
			const T *buffer_{ nullptr }; // Pointer to memory-mapped buffer
			size_t start_{ 0 };             // Start index of row content
			size_t end_{ 0 };               // End index of row content
			CSVInfos<T> *infos_{ nullptr };
			friend class RowIterator;
			friend class Reader;

		public:
			// Returns the raw_value of the row
			template <typename Container> void read_raw_value(Container &result) const {
				if (start_ >= end_)
					return;
				result.reserve(end_ - start_);
				for (size_t i = start_; i < end_; ++i)
					result.push_back(buffer_[i]);
			}

			inline const T* data() const {
				if (start_ >= end_) { return nullptr; }
				else { return &buffer_[start_]; }
			}

			inline const size_t size() const {
				return end_ - start_;
			}

			class CellIterator {
				friend class Row;
				const T *buffer_;
				size_t buffer_size_;
				size_t start_;
				size_t current_;
				size_t end_;
				CSVInfos<T> *infos_{ nullptr };

			public:
				CellIterator(const T *buffer, size_t buffer_size, size_t start, size_t end, CSVInfos<T> *infos)
					: buffer_(buffer), buffer_size_(buffer_size), start_(start), current_(start_), end_(end), infos_(infos) {
				}

				CellIterator &operator++() {
					current_ += 1;
					return *this;
				}

				Cell operator*() {
					class Cell cell;
					cell.buffer_ = buffer_;
					cell.start_ = current_;
					cell.end_ = end_;
					cell.infos_ = infos_;

					bool quote_opened = false;
					if ((buffer_[current_] == infos_->quote_character) && (current_ < end_)) {
						quote_opened = true;
						current_++;
					};

				splice_nextline_:
					for (auto i = current_; i < end_; i++) {
						current_ = i;
						if (buffer_[i] == infos_->delimiter && !quote_opened) {
							// actual delimiter
							// end of cell
							cell.end_ = current_;
							return cell;
						}
						else if (quote_opened && buffer_[i] == infos_->quote_character) {
							if (buffer_[i + 1] == infos_->quote_character) { i++; }
							else { quote_opened = false; }
						}
					}
					
					if (quote_opened) {
						while (buffer_[current_] != '\n' && current_ + 1 < infos_->buffer_size) { current_++; }
						current_++;
						if (current_ < infos_->buffer_size) {
							if (const T *ptr = memchr((T *)&buffer_[current_], '\n', (infos_->buffer_size - current_))) {
								end_ = current_ + (ptr - &buffer_[current_]);
								infos_->next_row_start = end_ + 1;
								end_ -= (*(ptr - 1) == '\r');
								cell.end_ = end_;
								infos_->current_row_end = cell.end_;
							}
							else {
								// last row
								end_ = infos_->buffer_size;
								cell.end_ = end_;
								infos_->current_row_end = end_;
								infos_->next_row_start = end_ + 1;
							}
							goto splice_nextline_;
						}
						else
							cell.end_ = infos_->buffer_size, infos_->current_row_end = cell.end_, infos_->next_row_start = cell.end_ + 1;
					}
					else { cell.end_ = current_ + 1; }
					return cell;
				}

				bool operator!=(const CellIterator &rhs) { return current_ < rhs.infos_->current_row_end; }
			};

			CellIterator begin() const { return CellIterator(buffer_, end_ - start_, start_, end_, infos_); }
			CellIterator end() const { return CellIterator(buffer_, end_ - start_, end_, end_, infos_); }
		};

		class RowIterator {
			friend class Reader;
			const T *buffer_;
			size_t buffer_size_;
			size_t start_;
			size_t end_;
			CSVInfos<T> *infos_{ nullptr };

		public:
			RowIterator(const T *buffer, size_t buffer_size, size_t start, CSVInfos<T> *infos)
				: buffer_(buffer), buffer_size_(buffer_size), start_(start), end_(start_), infos_(infos) {}

			RowIterator &operator++() {
				start_ = max(end_ + 1, infos_->next_row_start);
				end_ = start_;
				return *this;
			}

			Row operator*() {
				Row result;
				result.buffer_ = buffer_;
				result.start_ = start_;
				result.end_ = end_;
				result.infos_ = infos_;

				if (const T *ptr = memchr((T *)&buffer_[start_], '\n', (buffer_size_ - start_))) {
					end_ = start_ + (ptr - &buffer_[start_]);
					result.end_ = end_ - (*(ptr - 1) == '\r');
					start_ = end_ + 1;
					infos_->next_row_start = start_;
				}
				else {
					// last row
					end_ = buffer_size_;
					result.end_ = end_;
				}
				infos_->current_row_end = result.end_;
				return result;
			}

			bool operator!=(const RowIterator &rhs) { return start_ < rhs.start_; }
		};

		RowIterator begin() const {
			if (buffer_size_ == 0)
				return end();
			if (infos.next_row_start)
				return RowIterator(buffer_, buffer_size_, infos.next_row_start, (CSVInfos<T>*)&infos);
			else if (infos.first_row_is_header) {
				const auto header_indices = header_indices_();
				return RowIterator(buffer_, buffer_size_, header_indices.second > 0 ? header_indices.second + 1 : 0, (CSVInfos<T>*)&infos);
			}
			else {
				return RowIterator(buffer_, buffer_size_, removebom(), (CSVInfos<T>*)&infos);
			}
		}

		RowIterator end() const { return RowIterator(buffer_, buffer_size_, buffer_size_ + 1, (CSVInfos<T>*)&infos); }

	private:
		std::pair<size_t, size_t> header_indices_() const {
			size_t start = removebom(), end = 0;

			if (const T *ptr = memchr((T *)&buffer_[start], '\n', (buffer_size_ - start))) {
				end = start + (ptr - &buffer_[start]);
			}
			return { start, end };
		}
		size_t removebom() const {
			if (buffer_size_ < 3) { return 0; }
			if (sizeof(T) == 1) {
				if ((buffer_[0] & 0xFF) == 0xEF && (buffer_[1] & 0xFF) == 0xBB && (buffer_[2] & 0xFF) == 0xBF) { return 3; }
				else if (((buffer_[0] & 0xFF) == 0xFF && (buffer_[1] & 0xFF) == 0xFE)
					|| ((buffer_[0] & 0xFF) == 0xFE && (buffer_[1] & 0xFF) == 0xFF)) { return 2; }
			}
			else if ((buffer_[0] == 0xFFFE) || (buffer_[0] == 0xFEFF)) { return 1; }
			return 0;
		}

	public:

		Row header() const {
			size_t start = removebom(), end = 0;
			Row result;
			result.buffer_ = buffer_;
			result.start_ = start;
			result.end_ = end;

			if (const T *ptr = memchr((T *)&buffer_[start], '\n', (buffer_size_ - start))) {
				end = start + (ptr - &buffer_[start]);
				result.end_ = end;
			}
			return result;
		}

		size_t rows() const {
			size_t result{ 0 };
			if (!buffer_ || buffer_size_ == 0)
				return result;
			for (T *p = (T *)buffer_; (p = memchr((T *)p, '\n', (buffer_ + buffer_size_) - p)); ++p)
				++result;
			return result;
		}

		size_t cols() const {
			size_t result{ 0 };
			for (const auto cell : header())
				result += 1;
			return result;
		}
	};
} // namespace csvparse